package cn.song.freight.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.song.freight.domain.dto.*;
import cn.song.freight.domain.entity.*;
import cn.song.freight.domain.result.R;
import cn.song.freight.domain.vo.WeightSegmentRuleVO;
import cn.song.freight.mapper.FreightStrategyMapper;
import cn.song.freight.mapper.PricingAreaMappingMapper;
import cn.song.freight.mapper.StrategyAssignmentMapper;
import cn.song.freight.service.FreightStrategyService;
import cn.song.freight.service.PricingAreaMappingService;
import cn.song.freight.service.StrategyAreaPricingService;
import cn.song.freight.service.WeightSegmentRuleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 运费策略服务实现类
 * <pre>
 * 该服务实现提供物流系统的运费策略管理功能，支持策略的增删改查操作，
 * 并提供基于多种条件的策略查询功能，用于维护系统中的定价策略配置。
 *
 * 核心功能：
 * - 运费策略信息的添加、删除、更新和查询
 * - 支持多条件组合查询
 * - 数据完整性校验和业务规则验证
 * - 策略状态管理和使用情况检查
 *
 * 定价模型说明：
 * 1. 基本重量定价（pricingModel=1）：使用首重+续重的定价方式
 * 2. 混合模型定价（pricingModel=2）：结合重量定价和重量区间规则的复杂定价方式
 *
 * 策略状态说明：
 * 1. 启用状态（status=1）：策略可被系统选择和使用
 * 2. 禁用状态（status=2）：策略不可被系统选择和使用
 *
 * @author SongRenShuo
 * @version 3.0.0
 * @since 2025-08-23
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FreightStrategyServiceImpl extends ServiceImpl<FreightStrategyMapper, FreightStrategyDO> implements FreightStrategyService {

    /**
     * 重量区间规则服务，用于处理重量区间规则的批量删除
     */
    private final FreightStrategyMapper freightStrategyMapper;
    private final WeightSegmentRuleService weightSegmentRuleService;
    private final StrategyAssignmentMapper strategyAssignmentMapper;
    private final StrategyAreaPricingService strategyAreaPricingService;
    private final PricingAreaMappingService pricingAreaMappingService;
    private final PricingAreaMappingMapper pricingAreaMappingMapper;

    /**
     * 添加新的运费策略
     * <pre>
     * 该方法实现运费策略的添加功能，会自动设置创建时间和更新时间，
     * 并将策略信息保存到数据库中，供后续运费计算时使用。
     *
     * @param dto 运费策略添加数据传输对象，包含策略的基本信息
     * @return 添加结果，true表示添加成功，false表示添加失败
     */
    @Override
    public Boolean add(FreightStrategyAddDTO dto) {
        // 构建 DO 对象并填充属性
        FreightStrategyDO entity = new FreightStrategyDO();
        BeanUtils.copyProperties(dto, entity);
        entity.setCreateTime(new Date());
        entity.setUpdateTime(entity.getCreateTime());

        // 插入数据库
        return this.save(entity);
    }

    /**
     * 删除指定的运费策略
     * <pre>
     * 该方法实现运费策略的删除功能，会先校验策略是否存在，
     * 并检查该策略是否已被分配使用，避免误删正在使用的策略数据，
     * 确保系统的数据一致性和业务连续性。
     *
     * @param id 要删除的运费策略ID
     * @return 删除结果，true表示删除成功，false表示删除失败
     * @throws RuntimeException 如果策略不存在或已被分配使用
     */
    @Override
    public Boolean delete(Long id) {
        // 判断是否存在该运费策略
        Assert.notNull(this.getById(id), "策略不存在：" + id);

        // 检查该策略是否已被分配使用
        LambdaQueryWrapper<StrategyAssignmentDO> assignmentCheckWrapper = Wrappers.<StrategyAssignmentDO>lambdaQuery()
                .eq(StrategyAssignmentDO::getStrategyId, id);
        long count = strategyAssignmentMapper.selectCount(assignmentCheckWrapper);
        Assert.isTrue(count == 0, "策略已被分配无法删除");

        // 执行删除操作
        return this.removeById(id);
    }

    /**
     * 更新运费策略信息
     * <pre>
     * 该方法实现运费策略信息的更新功能，会先校验策略是否存在，
     * 自动设置更新时间，然后根据传入的更新数据对策略信息进行更新操作，
     * 保证策略信息的实时性和准确性。
     *
     * 特别处理：当计费模式从"首重续重+重量区间"(2)改为"首重续重"(1)时，
     * 需要清除该策略下的所有重量区间规则，因为纯"首重续重"模式不需要重量区间。
     *
     * @param dto 运费策略更新数据传输对象，包含要更新的策略ID和新信息
     * @return 更新结果，true表示更新成功，false表示更新失败
     * @throws RuntimeException 如果策略不存在
     */
    @Override
    public Boolean edit(FreightStrategyEditDTO dto) {
        // 校验输入参数是否合法
        FreightStrategyDO entity = Assert.notNull(this.getById(dto.getId()), "策略不存在：" + dto.getId());

        // 检查计费模式是否从"首重续重+重量区间"(2)改为"首重续重"(1)
        Integer originalPricingModel = entity.getPricingModel();
        Integer newPricingModel = dto.getPricingModel();

        boolean shouldClearWeightRules = originalPricingModel != null &&
                newPricingModel != null &&
                originalPricingModel.equals(2) &&
                newPricingModel.equals(1);

        if (shouldClearWeightRules) {
            log.info("检测到策略ID={}的计费模式从“首重续重+重量区间”改为“首重续重”，即将清除该策略下的所有重量区间规则", dto.getId());
            // 清除该策略下的所有重量区间规则
            R<Integer> result = weightSegmentRuleService.deleteByStrategyId(dto.getId());
            if (result.getCode() == 0) {
                log.info("成功清除策略ID={}下的{}个重量区间规则", dto.getId(), result.getData());
            } else {
                log.warn("清除策略ID={}下的重量区间规则失败：{}", dto.getId(), result.getMsg());
            }
        }

        // 更新字段
        BeanUtils.copyProperties(dto, entity);
        entity.setUpdateTime(new Date());

        // 执行更新操作
        return this.updateById(entity);
    }

    /**
     * 查询运费策略列表
     * <pre>
     * 该方法实现运费策略信息的条件查询功能，支持按名称、定价模型和状态进行查询，
     * 查询结果按创建时间倒序排列，便于查看最新的策略信息。
     *
     * @param query 运费策略查询数据传输对象，包含查询条件
     * @return 符合条件的运费策略列表
     */
    @Override
    public List<FreightStrategyDO> list(FreightStrategyQueryDTO query) {
        LambdaQueryWrapper<FreightStrategyDO> wrapper = Wrappers.<FreightStrategyDO>lambdaQuery()
                // 按名称模糊查询
                .like(StrUtil.isNotBlank(query.getName()), FreightStrategyDO::getName, query.getName())
                // 按定价模型精确匹配
                .eq(query.getPricingModel() != null, FreightStrategyDO::getPricingModel, query.getPricingModel())
                // 按状态精确匹配
                .eq(query.getStatus() != null, FreightStrategyDO::getStatus, query.getStatus())
                // 按创建时间倒序排列，方便查看最新策略
                .orderByDesc(FreightStrategyDO::getCreateTime);

        return this.list(wrapper);
    }

    /**
     * 查询运费策略详情
     * <pre>
     * 该方法根据策略ID获取指定运费策略的详细信息，
     * 用于查看策略的完整配置信息和当前状态，
     * 为策略编辑和详情展示提供数据支持。
     *
     * @param id 运费策略ID
     * @return 运费策略详细信息
     * @throws RuntimeException 如果策略不存在
     */
    @Override
    public FreightStrategyDO detail(Long id) {
        return Assert.notNull(this.getById(id), "策略不存在：" + id);
    }

    /**
     * 复制策略数据
     * <pre>
     * 该方法复制指定策略的所有配置信息（包括地区配置和重量区间规则），
     * 但不包括策略分配信息，用于前端编辑弹框的回显。
     *
     * @param copyDTO 复制策略数据传输对象
     * @return 复制操作是否成功
     * @throws RuntimeException 如果策略不存在
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean copyStrategyData(StrategyCopyDTO copyDTO) {
        Long id = copyDTO.getId();
        String operator = copyDTO.getOperator();
        // 获取原策略信息
        FreightStrategyDO originalStrategy = Assert.notNull(this.getById(id), "策略不存在：" + id);

        // 1. 复制策略基本信息
        Date createTime = new Date();
        FreightStrategyDO newStrategy = new FreightStrategyDO();
        BeanUtils.copyProperties(originalStrategy, newStrategy);
        newStrategy.setId(null);
        newStrategy.setName(copyDTO.getNewName());
        newStrategy.setDescription(copyDTO.getDescription());
        newStrategy.setCreateTime(createTime);
        newStrategy.setUpdateTime(createTime);
        newStrategy.setOperator(operator);

        // 保存新策略
        freightStrategyMapper.insert(newStrategy);
        Long newStrategyId = newStrategy.getId();

        // 2. 批量复制地区配置和区域映射关系
        List<StrategyAreaPricingDO> originalPricingConfigs = strategyAreaPricingService.getByStrategyId(id);
        Map<Long, Long> pricingIdMap = new HashMap<>();
        List<StrategyAreaPricingDO> newAreaConfigs = new ArrayList<>();
        List<PricingAreaMappingDO> newAreaMappings = new ArrayList<>();

        if (originalPricingConfigs != null && !originalPricingConfigs.isEmpty()) {
            for (StrategyAreaPricingDO originalConfig : originalPricingConfigs) {
                // 创建新的地区配置
                StrategyAreaPricingDO newAreaConfig = new StrategyAreaPricingDO();
                newAreaConfig.setStrategyId(newStrategyId);
                newAreaConfig.setName(originalConfig.getName());
                newAreaConfig.setDescription(originalConfig.getDescription());
                newAreaConfig.setFirstWeightKg(originalConfig.getFirstWeightKg());
                newAreaConfig.setFirstWeightFee(originalConfig.getFirstWeightFee());
                newAreaConfig.setAdditionalWeightKg(originalConfig.getAdditionalWeightKg());
                newAreaConfig.setAdditionalWeightFee(originalConfig.getAdditionalWeightFee());
                newAreaConfig.setVolumetricWeightRatio(originalConfig.getVolumetricWeightRatio());
                newAreaConfig.setOperator(operator);
                newAreaConfig.setCreateTime(createTime);
                newAreaConfig.setUpdateTime(createTime);

                newAreaConfigs.add(newAreaConfig);
                // 先占位，保存后更新
                pricingIdMap.put(originalConfig.getId(), null);
            }

            // 批量保存地区配置
            strategyAreaPricingService.saveBatch(newAreaConfigs);

            // 更新pricingId映射关系
            for (int i = 0; i < originalPricingConfigs.size(); i++) {
                StrategyAreaPricingDO originalConfig = originalPricingConfigs.get(i);
                StrategyAreaPricingDO newConfig = newAreaConfigs.get(i);
                pricingIdMap.put(originalConfig.getId(), newConfig.getId());
            }

            // 复制区域映射关系
            for (StrategyAreaPricingDO originalConfig : originalPricingConfigs) {
                List<Long> areaIds = pricingAreaMappingMapper.selectAreaIdsByPricingId(originalConfig.getId());
                if (areaIds != null && !areaIds.isEmpty()) {
                    Long newPricingId = pricingIdMap.get(originalConfig.getId());
                    for (Long areaId : areaIds) {
                        PricingAreaMappingDO newMapping = new PricingAreaMappingDO();
                        newMapping.setPricingId(newPricingId);
                        newMapping.setAreaId(areaId);
                        newMapping.setCreateTime(createTime);
                        newMapping.setOperator(operator);
                        newAreaMappings.add(newMapping);
                    }
                }
            }

            // 批量保存区域映射
            if (!newAreaMappings.isEmpty()) {
                pricingAreaMappingService.saveBatch(newAreaMappings);
            }
        }

        // 3. 批量复制重量规则
        List<WeightSegmentRuleDO> originalWeightRules = weightSegmentRuleService.list(
            Wrappers.<WeightSegmentRuleDO>lambdaQuery()
                .eq(WeightSegmentRuleDO::getStrategyId, id)
        );
        if (originalWeightRules != null && !originalWeightRules.isEmpty()) {
            List<WeightSegmentRuleDO> newWeightRules = new ArrayList<>();

            for (WeightSegmentRuleDO originalRule : originalWeightRules) {
                Long newPricingId = pricingIdMap.get(originalRule.getPricingId());
                if (newPricingId != null) {
                    WeightSegmentRuleDO newWeightRule = new WeightSegmentRuleDO();
                    newWeightRule.setStrategyId(newStrategyId);
                    newWeightRule.setPricingId(newPricingId);
                    newWeightRule.setUpperBound(originalRule.getUpperBound());
                    newWeightRule.setFreight(originalRule.getFreight());
                    newWeightRule.setCreateTime(createTime);
                    newWeightRule.setUpdateTime(createTime);
                    newWeightRule.setOperator(operator);
                    newWeightRules.add(newWeightRule);
                }
            }

            // 批量保存重量规则
            if (!newWeightRules.isEmpty()) {
                weightSegmentRuleService.saveBatch(newWeightRules);
            }
        }

        return true;
    }

    /**
     * 根据策略ID查询地区配置信息
     */
    private List<StrategyConfigUnifiedSaveRequestDTO.AreaConfigDTO> queryAreaConfigsByStrategyId(Long strategyId) {
        // 查询策略下的所有定价配置
        List<StrategyAreaPricingDO> pricingConfigs = strategyAreaPricingService.getByStrategyId(strategyId);

        // 转换为AreaConfigDTO格式
        return pricingConfigs.stream()
                .map(this::convertToAreaConfigDTO)
                .collect(Collectors.toList());
    }

    /**
     * 将StrategyAreaPricingDO转换为AreaConfigDTO
     */
    private StrategyConfigUnifiedSaveRequestDTO.AreaConfigDTO convertToAreaConfigDTO(StrategyAreaPricingDO pricingDO) {
        StrategyConfigUnifiedSaveRequestDTO.AreaConfigDTO dto = new StrategyConfigUnifiedSaveRequestDTO.AreaConfigDTO();
        // AreaConfigDTO 可能没有 setPricingId 方法，使用其他方式设置ID
        // dto.setPricingId(pricingDO.getId());
        dto.setName(pricingDO.getName());
        dto.setDescription(pricingDO.getDescription());
        dto.setFirstWeightKg(pricingDO.getFirstWeightKg() != null ? pricingDO.getFirstWeightKg().doubleValue() : null);
        dto.setFirstWeightFee(pricingDO.getFirstWeightFee() != null ? pricingDO.getFirstWeightFee().doubleValue() : null);
        dto.setAdditionalWeightKg(pricingDO.getAdditionalWeightKg() != null ? pricingDO.getAdditionalWeightKg().doubleValue() : null);
        dto.setAdditionalWeightFee(pricingDO.getAdditionalWeightFee() != null ? pricingDO.getAdditionalWeightFee().doubleValue() : null);
        dto.setVolumetricWeightRatio(pricingDO.getVolumetricWeightRatio() != null ? pricingDO.getVolumetricWeightRatio().doubleValue() : null);
        dto.setOperator(pricingDO.getOperator());

        // 查询定价配置对应的区域映射关系
        List<Long> areaIds = pricingAreaMappingMapper.selectAreaIdsByPricingId(pricingDO.getId());
        dto.setSelectedAreaIds(areaIds != null ? areaIds : Arrays.asList());

        return dto;
    }

    /**
     * 根据策略ID查询重量规则信息
     */
    private List<StrategyConfigUnifiedSaveRequestDTO.WeightRuleDTO> queryWeightRulesByStrategyId(Long strategyId) {
        // 查询策略下的所有重量规则
        List<WeightSegmentRuleVO> weightRules = weightSegmentRuleService.listByStrategyId(strategyId);

        // 转换为WeightRuleDTO格式
        return weightRules.stream()
                .map(this::convertToWeightRuleDTO)
                .collect(Collectors.toList());
    }

    /**
     * 将WeightSegmentRuleVO转换为WeightRuleDTO
     */
    private StrategyConfigUnifiedSaveRequestDTO.WeightRuleDTO convertToWeightRuleDTO(WeightSegmentRuleVO ruleVO) {
        StrategyConfigUnifiedSaveRequestDTO.WeightRuleDTO dto = new StrategyConfigUnifiedSaveRequestDTO.WeightRuleDTO();
        dto.setId(ruleVO.getId());
        dto.setStrategyId(ruleVO.getStrategyId());
        dto.setPricingId(ruleVO.getPricingId());
        dto.setUpperBound(ruleVO.getUpperBound() != null ? ruleVO.getUpperBound().doubleValue() : null);
        dto.setFreight(ruleVO.getFreight() != null ? ruleVO.getFreight().doubleValue() : null);
        return dto;
    }
}
